﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Threading;
using Engine.NetClasses;
namespace Engine.Client
{

    public class BaseClient
    {
        private Socket m_Socket;
        List<PacketsBilder> fromServer = new List<PacketsBilder>(0);
        List<PacketsBilder> fromClient = new List<PacketsBilder>(0);
        private DataReader m_dataReader;
        public  ulong ssesionKey = 0;
        private object locker = new object();
        private object lockerServer = new object();
        private Helpers.BlowFish cr;
       /// <summary>
       /// Получает все пакеты подготовенные на отправку
       /// и очишает хранилище пакетов
       /// </summary>
        /// <returns>Массив PacketsBilder </returns>
        public PacketsBilder[] GetFromClient()
        {

            PacketsBilder[] tmp;
            lock (locker)
            {
                tmp = fromClient.ToArray();
                fromClient.Clear();
                fromClient.TrimExcess();
                fromClient = new List<PacketsBilder>(0);
            }
            return tmp;

        }
        /// <summary>
        /// Добавить в хранилише пакетов на отправку подготовленный пакет
        /// </summary>
        /// <param name="FromClient">Пакет на отправку</param>
        public void SetFromClient(PacketsBilder FromClient)
        {


            lock (locker)
            {

                fromClient.Add(FromClient);
            }


        }
        /// <summary>
        /// Получает все пакеты пришедшие от сервера
        /// и очишает хранилище пакетов
        /// </summary>
        /// <returns>Массив PacketsBilder </returns>
       
        public PacketsBilder[] GetFromServer()
        {

            PacketsBilder[] tmp;
            lock (lockerServer)
            {
                tmp = fromServer.ToArray();
                fromServer.Clear();
            }
            return tmp;

        }
        /// <summary>
        /// Добавить в хранилише пакетов пришедший от сервера пакет
        /// </summary>
        /// <param name="FromClient">Пакет от сервера</param>
        
        public void SetFromServer(PacketsBilder FromServer)
        {


            lock (lockerServer)
            {

                fromServer.Add(FromServer);
            }


        }
        private IPAddress m_IP;
        private int m_port;
        private int m_bufferSize;
        private bool serverSendInit = false;
        private SocketAsyncEventArgs m_reciveEventArgs;
        private byte[] m_buffer;
        private uint m_sessionID;

        /// <summary>
        /// Клиент получил и прочетал от сервера пакет INIT
        /// </summary>
        public bool ServerSendInit
        {
            get { return serverSendInit; }
        }
        /// <summary>
        /// Установлино и соединение с сервером
        /// </summary>
        public bool Connected
        {
            get {
                if (m_Socket == null)
                {
                    return false;
                }
                else
                {
                    return m_Socket.Connected;
                }
            }
        }
/// <summary>
/// Установить (получить) размер буфера приемки
/// </summary>
        public int BufferSize
        {
            get { return m_bufferSize; }
            set { m_bufferSize = value; }
        }     
        /// <summary>
        /// установить получить порт подключения к серверу
        /// </summary>
        public int Port
        {
            get { return m_port; }
            set { m_port = value; }
        }        
        /// <summary>
        /// устоновить получить IP  подключения к серверу
        /// </summary>
        public string IP
        {
            get { return m_IP.ToString(); }
            set
            {
                IPAddress tmp;
                if (IPAddress.TryParse(value, out tmp))
                {
                    m_IP = tmp;
                }
                else
                {
                    throw new Exception("Bad string IP format(xxx.xxx.xxx.xxx)");
                }
            }
        }
        /// <summary>
        /// Количество пакетов в хранилище от клиента
        /// </summary>
        public int CountFromClient
        {
            get
            {
                int c = 0;
                lock (locker)
                {
                    c = fromClient.Count;
                }
                return c;
            }
        }
        /// <summary>
        /// Колличество пакетов в хранилище от сервера
        /// </summary>
        public int CountFromServer
        {
            get
            {
                int c = 0;
                lock (lockerServer)
                {
                    c = fromServer.Count;
                }
                return c;
            }
        }
        /// <summary>
        /// Создать клиент
        /// </summary>
        /// <param name="ip">строка IP (ххх.ххх.ххх.ххх)</param>
        /// <param name="port">порт сервера</param>
        /// <param name="defSize">размер буфера приема по умолчанию 4кб</param>
        public BaseClient(string ip,int port,int defSize=4096)
        {
            IP = ip;
            m_port = port;
            m_bufferSize = defSize;
        }
        /// <summary>
        /// Создать клиент
        /// </summary>
        /// <param name="ip">IPAddress IP аддрес сервера</param>
        /// <param name="port">порт сервера</param>
        /// <param name="defSize">размер буфера приема по умолчанию 4кб</param>
        public BaseClient(IPAddress ip, int port, int defSize = 4096)
        {
            
            m_IP = ip;
            m_port = port;
            m_bufferSize = defSize;
        }
        /// <summary>
        /// установить  IP и port подключения
        /// </summary>
        /// <param name="ip">строка адреса сервера</param>
        /// <param name="port">порт сервера</param>
        public void SetIPandPort(string ip, int port)
        {
            IP = ip;
            m_port = port;
        }
        /// <summary>
        /// главный цикл обновления 
        /// </summary>
        /// <param name="gameTime"></param>
        public virtual void Update(Microsoft.Xna.Framework.GameTime gameTime)
        {
            SendData();
        }
        
        /// <summary>
        /// A single callback is used for all socket operations. This method forwards execution on to the correct handler 
        /// based on the type of completed operation
        /// </summary>
        void SocketEventArg_Completed(object sender, SocketAsyncEventArgs e)
        {
            switch (e.LastOperation)
            {
                case SocketAsyncOperation.Connect:
                    ProcessConnect(e);
                   
                    break;
                case SocketAsyncOperation.Receive:                                        
                    ProcessReceive(e);
                    break;
                case SocketAsyncOperation.Send:                    
                    ProcessSend(e);
                    break;
                case SocketAsyncOperation.Disconnect:
                    serverSendInit = false;                   
                    CloseSocket(e);
                    break;

                default:
                    throw new Exception("Invalid operation completed");
            }
        }
        /// <summary>
        /// Асинхроный метод закрытия сокета
        /// </summary>
        /// <param name="e"></param>
        private void CloseSocket(SocketAsyncEventArgs e)
        {
            try
            {
                m_Socket.Shutdown(SocketShutdown.Send);
            }
            catch (Exception)
            {

            }
            m_Socket.Close();
        }

        /// <summary>
        /// Асинхронный метод отправки
        /// </summary>
        /// <param name="e"></param>
        private void ProcessSend(SocketAsyncEventArgs e)
        {
            if (e.SocketError == SocketError.Success)
            {



            }
            else
            {

                serverSendInit = false;
                CloseSocket(m_reciveEventArgs);
            }
        }
        /// <summary>
        /// Асинхронный метод получения
        /// </summary>
        /// <param name="e"></param>
 
        private void ProcessReceive(SocketAsyncEventArgs e)
        {
            if (e.BytesTransferred > 0 && e.SocketError == SocketError.Success && m_Socket.Connected)
            {

                readData(e);
                if (CountFromClient > 0)
                {
                    SendData();
                }
                if (e.SocketError!=SocketError.Disconnecting && m_Socket.Connected)
                {                    
                    bool willRaiseEvent = m_Socket.ReceiveAsync(e);
                    if (!willRaiseEvent)
                    {
                        ProcessReceive(e);
                    }
                }
                
            }

            else
            {
               
                serverSendInit = false;
                CloseSocket(e);

            }
        }
        /// <summary>
        /// Асинхронный метод подключения
        /// </summary>
        /// <param name="e"></param>
 
        private void ProcessConnect(SocketAsyncEventArgs e)
        {
            if (e.SocketError == SocketError.Success)
            {
                Console.WriteLine("Successfully connected to the server");
                m_buffer = new byte[m_bufferSize];
                e.SetBuffer(m_buffer, 0, m_buffer.Length);
                clientDone.Set();
            }
            else
            {
                clientDone.Set();
               // throw new SocketException((int)e.SocketError);
            }
        }
        /// <summary>
        /// Чтения буфера полученных данных
        /// и разбор а пакеты
        /// </summary>
        /// <param name="e"></param>
        private void readData(SocketAsyncEventArgs e)
        {
            byte[][] tmp = m_dataReader.read(e.Buffer, e.Offset, e.BytesTransferred, e.Count);
            if (tmp != null)
            {
                for (int i = 0; i < tmp.Length; i++)
                {
                    PacketsBilder pb = new PacketsBilder(tmp[i], 0);
                    if (!checkPackStandart(pb))
                    {
                        SetFromServer(pb);
                    }


                }
            }
        }
        /// <summary>
        /// Проверка на стандартные пакеты 
        /// Init and NetPing
        /// </summary>
        /// <param name="pb"></param>
        /// <returns></returns>
        private bool checkPackStandart(PacketsBilder pb)
        {
            if (pb.Header._ptype == 1 && !serverSendInit)
            {

               

                
                Init p = pb.getStryct();
                if (p.Protocol == 713)
                {
                    m_sessionID = p.ID;
                    serverSendInit = true;
                    SetFromServer(pb);
                }
                return true;

            }
             if (pb.Header._ptype == 5 && serverSendInit)
            {
                SetFromServer(pb);
                Ping pingSender = new Ping();
                PingOptions options = new PingOptions();

                // Use the default Ttl value which is 128,
                // but change the fragmentation behavior.
                options.DontFragment = true;

                // Create a buffer of 32 bytes of data to be transmitted.
                string data = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
                byte[] buffer = Encoding.ASCII.GetBytes(data);
                int timeout = 120;
                PingReply reply = pingSender.Send(m_IP, timeout, buffer, options);
                if (reply.Status == IPStatus.Success)
                {
                    NetPing pingPack = pb.getStryct();
                    pingPack.Ping = (int)reply.RoundtripTime;
                    SetFromServer(new PacketsBilder(pingPack));
                    
                }
                return true;
            }
             return false;
        }

        /// <summary>
        /// Отправка всех пакетов из хранилища на сервер
        /// </summary>
        private void SendData()
        {
            if (CountFromClient>0)
            {
                PacketsBilder[] tmp = null;
                tmp = GetFromClient();




                for (int i = 0; i < tmp.Length; i++)
                {
                    SocketAsyncEventArgs e = new SocketAsyncEventArgs();
                    e.UserToken = m_Socket;
                    e.Completed += new EventHandler<SocketAsyncEventArgs>(SocketEventArg_Completed);
                    if (ssesionKey!=0 && cr==null )
                    {
                        cr=new Helpers.BlowFish(BitConverter.GetBytes(ssesionKey));
                    }
                    byte[] buffer;
                    if (cr != null && ((IPacket)tmp[i].getStryct()).DoubleCrypt)
                    {
                        buffer = tmp[i].getPacket(cr);
                    }
                    else
                    {
                        buffer = tmp[i].getPacket();
                    }
                    // Array.Copy(tmp[i], 0, buffer, e.Offset, tmp[i].Length);
                    e.SetBuffer(buffer, 0, buffer.Length);

                    SendAsyn(e);
                }
            }
        }
        /// <summary>
        /// асинхронный метод отправки
        /// </summary>
        /// <param name="e"></param>
        private void SendAsyn(SocketAsyncEventArgs e)
        {
            if (e.SocketError == SocketError.Success && m_Socket.Connected)
            {
                bool willRaiseEvent = m_Socket.SendAsync(e);
                if (!willRaiseEvent)
                {

                    ProcessSend(e);
                }
            }
        }
        /// <summary>
        /// Запуск клиента
        /// </summary>
        public void Start()
        {
            NetPacket.NetPacketBild();

            IPAddress destinationAddr = m_IP;          // IP Address of server to connect to
            int destinationPort = m_port;                   // Port number of server
            m_reciveEventArgs = new SocketAsyncEventArgs();
            m_dataReader = new DataReader();
            if (m_bufferSize==0)
            {
                m_bufferSize = 10 * 1024;// set 10 kb bufer
            }


            // Create a socket and connect to the server
            m_Socket = new Socket(destinationAddr.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
            m_reciveEventArgs.Completed += new EventHandler<SocketAsyncEventArgs>(SocketEventArg_Completed);

            m_reciveEventArgs.RemoteEndPoint = new IPEndPoint(destinationAddr, destinationPort);
            m_reciveEventArgs.UserToken = m_Socket;

            m_Socket.ConnectAsync(m_reciveEventArgs);
            clientDone = new ManualResetEvent(false);
            clientDone.WaitOne();
            if (m_Socket.Connected)
            {
                
            
            bool willRaiseEvent = m_Socket.ReceiveAsync(m_reciveEventArgs);

            if (!willRaiseEvent)
            {
                ProcessReceive(m_reciveEventArgs);
            }
            }
            
        }
        ManualResetEvent clientDone = new ManualResetEvent(false);
        /// <summary>
        /// Выключение Клиента
        /// </summary>
        public void ShutDown()
        {
            // Data has now been sent and received from the server. Disconnect from the server
            // Socket sock = e.UserToken as Socket;
            if (m_Socket != null || Connected == true)
            {


                if (m_Socket.Connected)
                {
                    m_Socket.Shutdown(SocketShutdown.Send);
                    m_Socket.Close();
                }
            }
            //clientDone.Set();
        }
        public void Reconnect()
        {
            // Data has now been sent and received from the server. Disconnect from the server
            // Socket sock = e.UserToken as Socket;
            if (m_Socket != null || Connected == true)
            {


                if (m_Socket.Connected)
                {
                    m_Socket.Disconnect(true);
                    m_Socket.Close();
                   // m_Socket.Close();
                }
            }
            Thread.Sleep(1000);
            Start();
        }

    }
}
